From 84b1d17215c44df1fe4fad43e3d3a74519621259 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Mon, 12 May 2025 17:26:59 +0200 Subject: [PATCH] CVE-2025-0838 - Heap buffer overflow vulnerable in Abseil-cpp Origin: https://github.com/abseil/abseil-cpp/commit/5a0e2cb5e3958dd90bb8569a2766622cb74d90c1 Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1098903 Backported for bookworm from upstream. From 5a0e2cb5e3958dd90bb8569a2766622cb74d90c1 Mon Sep 17 00:00:00 2001 From: Derek Mauro Date: Thu, 23 Jan 2025 06:33:43 -0800 Subject: [PATCH] Fix potential integer overflow in hash container create/resize The sized constructors, reserve(), and rehash() methods of absl::{flat,node}_hash_{set,map} did not impose an upper bound on their size argument. As a result, it was possible for a caller to pass a very large size that would cause an integer overflow when computing the size of the container's backing store. Subsequent accesses to the container might then access out-of-bounds memory. The fix is in two parts: 1) Update max_size() to return the maximum number of items that can be stored in the container 2) Validate the size arguments to the constructors, reserve(), and rehash() methods, and abort the program when the argument is invalid We've looked at uses of these containers in Google codebases like Chrome, and determined this vulnerability is likely to be difficult to exploit. This is primarily because container sizes are rarely attacker-controlled. The bug was discovered by Dmitry Vyukov . PiperOrigin-RevId: 718841870 Change-Id: Ic09dc9de140a35dbb45ab9d90f58383cf2de8286 Gbp-Pq: Name CVE-2025-0838.patch --- absl/container/internal/raw_hash_set.cc | 5 +++ absl/container/internal/raw_hash_set.h | 33 +++++++++++++++++++- absl/container/internal/raw_hash_set_test.cc | 8 +++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index c63a2e0..c2a4c17 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "absl/container/internal/raw_hash_set.h" +#include "absl/base/internal/raw_logging.h" #include #include @@ -66,6 +67,10 @@ void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { // Extern template instantiotion for inline function. template FindInfo find_first_non_full(const ctrl_t*, size_t, size_t); +void HashTableSizeOverflow() { + ABSL_RAW_LOG(FATAL, "Hash table size overflow"); +} + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index ea912f8..70eb597 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -219,6 +219,15 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { +#ifdef ABSL_SWISSTABLE_ASSERT +#error ABSL_SWISSTABLE_ASSERT cannot be directly set +#else +// We use this macro for assertions that users may see when the table is in an +// invalid state that sanitizers may help diagnose. +#define ABSL_SWISSTABLE_ASSERT(CONDITION) \ + assert((CONDITION) && "Try enabling sanitizers.") +#endif + template void SwapAlloc(AllocType& lhs, AllocType& rhs, std::true_type /* propagate_on_container_swap */) { @@ -745,6 +754,15 @@ inline size_t NormalizeCapacity(size_t n) { return n ? ~size_t{} >> countl_zero(n) : 1; } +template +size_t MaxValidCapacity() { + return NormalizeCapacity((std::numeric_limits::max)() / 4 / + kSlotSize); +} + +// Use a non-inlined function to avoid code bloat. +[[noreturn]] void HashTableSizeOverflow(); + // General notes on capacity/growth methods below: // - We use 7/8th as maximum load factor. For 16-wide groups, that gives an // average of two empty slots per group. @@ -1148,6 +1166,10 @@ class raw_hash_set { : ctrl_(EmptyGroup()), settings_(0, HashtablezInfoHandle(), hash, eq, alloc) { if (bucket_count) { + if (ABSL_PREDICT_FALSE(bucket_count > + MaxValidCapacity())) { + HashTableSizeOverflow(); + } capacity_ = NormalizeCapacity(bucket_count); initialize_slots(); } @@ -1341,7 +1363,9 @@ class raw_hash_set { bool empty() const { return !size(); } size_t size() const { return size_; } size_t capacity() const { return capacity_; } - size_t max_size() const { return (std::numeric_limits::max)(); } + size_t max_size() const { + return CapacityToGrowth(MaxValidCapacity()); + } ABSL_ATTRIBUTE_REINITIALIZES void clear() { // Iterating over this container is O(bucket_count()). When bucket_count() @@ -1678,6 +1702,9 @@ class raw_hash_set { auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size())); // n == 0 unconditionally rehashes as per the standard. if (n == 0 || m > capacity_) { + if (ABSL_PREDICT_FALSE(m > MaxValidCapacity())) { + HashTableSizeOverflow(); + } resize(m); // This is after resize, to ensure that we have completed the allocation @@ -1688,6 +1715,9 @@ class raw_hash_set { void reserve(size_t n) { if (n > size() + growth_left()) { + if (ABSL_PREDICT_FALSE(n > max_size())) { + HashTableSizeOverflow(); + } size_t m = GrowthToLowerboundCapacity(n); resize(NormalizeCapacity(m)); @@ -2361,5 +2391,6 @@ ABSL_NAMESPACE_END } // namespace absl #undef ABSL_INTERNAL_ASSERT_IS_FULL +#undef ABSL_SWISSTABLE_ASSERT #endif // ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_ diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index f77ffbc..078bbad 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -2181,6 +2181,14 @@ TEST(Table, AlignOne) { } } +TEST(Table, MaxSizeOverflow) { + size_t overflow = (std::numeric_limits::max)(); + EXPECT_DEATH_IF_SUPPORTED(IntTable t(overflow), "Hash table size overflow"); + IntTable t; + EXPECT_DEATH_IF_SUPPORTED(t.reserve(overflow), "Hash table size overflow"); + EXPECT_DEATH_IF_SUPPORTED(t.rehash(overflow), "Hash table size overflow"); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END -- 2.30.2